package cloudspace.vm.io.console;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;


/**
 * This class is used for Memory Managed Line caches. Basically, the cache uses
 * a circular array underneath to store lines and drop them off the back end of
 * the array once the memory cache has reached its max size. To allow other
 * classes to access these lines without understanding the fact that lines are
 * removed automatically, the cache implements an overall index. This counter is
 * started from the creation of the cache and only flushed when it reaches
 * Long.MAX_SiZE. If this happens, a FlushException is thrown. This class is
 * based on a list; however, there are alot of functions not supported in this
 * cache therefore it does not implement the interface.
 * 
 * @author Mike Woods
 * 
 */
public class MemoryManagedLineCache
{
    private InfoStreamLine[] backend;

    private int startIndex;

    private int tailIndex;

    private int max;

    private int size;

    private long lowIndex;


    /**
     * This returns the lowest stored index in the cache.
     * 
     * @return
     */
    public long getStartIndex()
    {
        return lowIndex;
    }


    private void incTail()
    {
        if ( tailIndex == startIndex && size == max )
        {
            startIndex = ( startIndex + 1 ) % max;
            lowIndex++;
        }
        tailIndex = ( tailIndex + 1 ) % max;

    }


    private void incSize()
    {
        if ( size < max )
        {
            size++;
        }
    }


    private int convertIndex( long userIndex )
    {
        long reducedIndex = userIndex - lowIndex;
        return (int)( reducedIndex + startIndex ) % max;
    }


    /**
     * Create a new Managed Cache with a specified maximum storage size.
     * 
     * @param maxSize
     *            the maximum size of the cache underneath.
     */
    public MemoryManagedLineCache( int maxSize )
    {
        max = maxSize;
        init();
    }


    private void init()
    {
        backend = new InfoStreamLine[max];
        startIndex = 0;
        tailIndex = 0;
        size = 0;
    }


    /**
     * Add a line to the cache
     * 
     * @param e
     *            line to add
     * @return true if the line was added.
     */
    public synchronized boolean add( InfoStreamLine e )
    {
        backend[tailIndex] = e;
        incTail();
        incSize();
        return true;
    }


    /**
     * Adds a collection of Lines to the cache.
     * 
     * @param c
     *            the collection to add
     * @return true if the line were added.
     */
    public synchronized boolean addAll( Collection<? extends InfoStreamLine> c )
    {
        Iterator<? extends InfoStreamLine> iter = c.iterator();
        while ( iter.hasNext() )
        {
            backend[tailIndex] = iter.next();
            incTail();
            incSize();
        }
        return true;
    }


    /**
     * Test to see if the cache is empty
     * 
     * @return true if the cache is empty
     */
    public synchronized boolean isEmpty()
    {
        return size == 0;
    }


    /**
     * Access an iterator for the entire cache.
     * 
     * @return an iterator for all of the elements stored in the cache.
     */
    public synchronized Iterator<InfoStreamLine> iterator()
    {
        InfoStreamLine[] reorder = new InfoStreamLine[size];
        long realIndex = lowIndex;
        for ( int i = 0; i < size; i++ )
        {
            reorder[i % size] = backend[convertIndex( realIndex )];
            realIndex++;
        }
        return Arrays.asList( reorder ).iterator();
    }


    /**
     * Returns the actual max size of the list. This takes into account the
     * number of elements that have been dropped from the cache.
     * 
     * @return the number of elements that have been stored and are stored in
     *         the cache
     */
    public synchronized long size()
    {
        return lowIndex + size;
    }


    /**
     * Gets the element stored at the specified index
     * 
     * @param index
     *            the index of the item.
     * @return the info stream line that was stored at the given index.
     */
    public synchronized InfoStreamLine get( long index )
    {
        if ( index < lowIndex )
        {
            throw new ArrayIndexOutOfBoundsException( "The index passed to get is too low." );
        }
        if ( index > max )
        {
            throw new ArrayIndexOutOfBoundsException( "The index passed to get is too high." );
        }
        return backend[convertIndex( index )];
    }


    /**
     * Gets the sublist at the specified indexes
     * 
     * @param fromIndex
     *            the start index for the get command
     * @param toIndex
     *            the end index of the start command.
     * @return the sub list
     */
    public synchronized List<InfoStreamLine> subList(
        long fromIndex,
        long toIndex )
    {
        long from = fromIndex;
        if ( from < lowIndex )
        {
            from = lowIndex;
        }
        if ( toIndex > lowIndex + size )
        {
            toIndex = max;
        }
        List<InfoStreamLine> subList = new ArrayList<InfoStreamLine>();
        for ( long i = from; i < toIndex; i++ )
        {
            int realIndex = convertIndex( i );
            subList.add( backend[realIndex] );
        }
        return subList;
    }


    /**
     * Returns a sorted array of the circular array stored underneath.
     * 
     * @return the sorted array of lines.
     */
    public synchronized Object[] toArray()
    {
        Object[] reorder = new Object[size];
        for ( int i = 0; i < size; i++ )
        {
            reorder[i] = backend[convertIndex( i )];
        }
        return reorder;
    }

}